package com.zg.datapush.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zg.common.lock.service.LockService;
import com.zg.datapush.constant.DataPushConstants;
import com.zg.common.core.dao.datapush.dao.DpPushTaskDao;
import com.zg.common.core.dao.datapush.entity.DpPushTask;
import com.zg.datapush.util.CronUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.sql.Timestamp;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * 任务调度服务
 * @author zg
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class TaskScheduleService {

    private final DpPushTaskDao pushTaskDao;
    private final PushService pushService;
    private final LockService lockService;

    private final ThreadPoolTaskExecutor executor;

    /**
     * 定时扫描需要执行的任务（每分钟执行一次）
     */
    @Scheduled(fixedRate = 60000) // 每60秒执行一次
    public void scanAndExecuteTasks() {
        String lockKey = DataPushConstants.LOCK_KEY_SCHEDULE_SCAN;
        boolean locked = false;

        try {
            // 尝试获取分布式锁，防止多实例重复执行
            locked = lockService.tryLock(lockKey, 5, 30, TimeUnit.SECONDS);
            if (!locked) {
                log.debug("获取扫描锁失败，跳过本次扫描");
                return;
            }

            log.debug("开始扫描待执行的推送任务");
            doScanAndExecuteTasks();

        } catch (Exception e) {
            log.error("扫描推送任务失败", e);
        } finally {
            if (locked) {
                try {
                    lockService.unlock(lockKey);
                } catch (Exception e) {
                    log.error("释放扫描锁失败", e);
                }
            }
        }
    }

    /**
     * 执行任务扫描的具体逻辑
     */
    private void doScanAndExecuteTasks() {
        // 查询所有启用的、需要执行的任务
        List<DpPushTask> tasks = getTasksToExecute();

        if (tasks.isEmpty()) {
            log.debug("没有需要执行的推送任务");
            return;
        }

        log.info("找到 {} 个待执行的推送任务", tasks.size());

        for (DpPushTask task : tasks) {
            try {
                // 为每个任务获取独立的锁
                executeTaskWithLock(task);
            } catch (Exception e) {
                log.error("启动推送任务失败: {}", task.getTaskName(), e);
            }
        }
    }

    /**
     * 带锁执行单个任务
     */
    private void executeTaskWithLock(DpPushTask task) {
        String taskLockKey = DataPushConstants.LOCK_KEY_TASK_PREFIX + task.getId();

        // 使用专用线程池异步执行任务
        CompletableFuture.runAsync(() -> {
            boolean taskLocked = false;
            try {
                // 为单个任务获取锁，防止同一任务被重复执行
                taskLocked = lockService.tryLock(taskLockKey, 1, 300, TimeUnit.SECONDS);
                if (!taskLocked) {
                    log.debug("任务 {} 正在执行中，跳过", task.getTaskName());
                    return;
                }

                log.info("开始执行定时推送任务: {} (调度类型: {})", task.getTaskName(), task.getScheduleType());

                // 执行定时推送
                pushService.executePushTaskByCron(task.getId());

                // 更新下次执行时间
                updateNextExecuteTime(task);

            } catch (Exception e) {
                log.error("定时推送任务执行失败: {}", task.getTaskName(), e);
            } finally {
                if (taskLocked) {
                    try {
                        lockService.unlock(taskLockKey);
                    } catch (Exception e) {
                        log.error("释放任务锁失败: {}", task.getTaskName(), e);
                    }
                }
            }
        }, executor);
    }

    /**
     * 获取需要执行的任务
     */
    private List<DpPushTask> getTasksToExecute() {
        return pushTaskDao.list(
            new LambdaQueryWrapper<DpPushTask>()
                .eq(DpPushTask::getStatus, DataPushConstants.TASK_STATUS_ENABLED) // 启用状态
                .eq(DpPushTask::getScheduleType, DataPushConstants.SCHEDULE_TYPE_CRON) // 只支持定时调度
                .and(wrapper -> wrapper
                    .le(DpPushTask::getNextExecuteTime, new Date()) // 到达执行时间
                    .or()
                    .isNull(DpPushTask::getNextExecuteTime) // 或者还没有设置下次执行时间
                )
        );
    }

    /**
     * 更新任务的下次执行时间
     */
    private void updateNextExecuteTime(DpPushTask task) {
        try {
            Timestamp nextTime = calculateNextExecuteTime(task);
            if (nextTime != null) {
                task.setNextExecuteTime(nextTime);
                pushTaskDao.updateById(task);
                log.debug("更新任务 {} 的下次执行时间: {}", task.getTaskName(), nextTime);
            }
        } catch (Exception e) {
            log.error("更新任务下次执行时间失败: {}", task.getTaskName(), e);
        }
    }

    /**
     * 计算下次执行时间
     */
    private Timestamp calculateNextExecuteTime(DpPushTask task) {
        if (DataPushConstants.SCHEDULE_TYPE_CRON.equals(task.getScheduleType()) && task.getCronExpression() != null) {
            // Cron调度：解析cron表达式计算下次执行时间
            return calculateNextCronTime(task.getCronExpression());
        }

        return null;
    }

    /**
     * 根据cron表达式计算下次执行时间
     */
    private Timestamp calculateNextCronTime(String cronExpression) {
        try {
            Date nextTime = CronUtil.getNextExecuteTime(cronExpression);
            return nextTime != null ? new Timestamp(nextTime.getTime()) :
                   new Timestamp(System.currentTimeMillis() + 3600000);
        } catch (Exception e) {
            log.error("解析cron表达式失败: {}", cronExpression, e);
            return new Timestamp(System.currentTimeMillis() + 3600000);
        }
    }

    /**
     * 启动任务调度（设置初始的下次执行时间）
     */
    public void startTaskSchedule(Long taskId) {
        DpPushTask task = pushTaskDao.getById(taskId);
        if (task != null && DataPushConstants.TASK_STATUS_ENABLED.equals(task.getStatus())) {
            Timestamp nextTime = calculateNextExecuteTime(task);
            if (nextTime != null) {
                task.setNextExecuteTime(nextTime);
                pushTaskDao.updateById(task);
                log.info("启动任务调度: {}, 下次执行时间: {}", task.getTaskName(), nextTime);
            }
        }
    }

    /**
     * 停止任务调度（清除下次执行时间）
     */
    public void stopTaskSchedule(Long taskId) {
        DpPushTask task = pushTaskDao.getById(taskId);
        if (task != null) {
            task.setNextExecuteTime(null);
            pushTaskDao.updateById(task);
            log.info("停止任务调度: {}", task.getTaskName());
        }
    }
}
